/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.data.xml.xsd.resolver;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.UUID;

/**
 * Enhanced XML entity resolver.
 * <p>
 * XML entity resolver provides new instance method to avoid some STATEful
 * operations' conflicting.<br/>
 * </p>
 * 
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 18 Jul, 2014
 */
public class EntityResolver implements org.xml.sax.EntityResolver {

	private String mainXsdPath;

	private String encoding = "UTF-8";

	private String baseDir;

	private EntityResolver(String mainXsdPath, String encoding) {
		if (encoding != null) {
			this.encoding = encoding;
		}
		baseDir = new File(".").getAbsolutePath();
		if (baseDir.startsWith("/")) {
			baseDir = baseDir.substring(1);
		}
		if (mainXsdPath != null) {
			if (mainXsdPath.toLowerCase().endsWith(".xsd")) {
				int index = mainXsdPath.lastIndexOf('/');
				if (index == -1)
					index = mainXsdPath.lastIndexOf('\\');
				if (index == -1) {
					mainXsdPath = "";
				} else {
					mainXsdPath = mainXsdPath.substring(0, index);
				}
			}
			this.mainXsdPath = mainXsdPath;
		}
	}

	/**
	 * Create a new instance of EntityResolver.
	 * <p>
	 * Provide a consistent API to create a instance
	 * </p>
	 * 
	 * @param mainXsdPath
	 *            Main XSD path which can be refer by the EntityResolver
	 * @return New instance of EntityResolver.
	 */
	public static EntityResolver newInstance(String mainXsdPath) {
		return newInstance(mainXsdPath, null);
	}

	/**
	 * Create a new instance of EntityResolver.
	 * <p>
	 * Provide a consistent API to create a instance
	 * </p>
	 * 
	 * @param mainXsdPath
	 *            Main XSD path which can be refer by the EntityResolver
	 * @param encoding
	 *            Encoding of XSDs
	 * @return New instance of EntityResolver.
	 */
	public static EntityResolver newInstance(String mainXsdPath, String encoding) {
		EntityResolver resolver = new EntityResolver(mainXsdPath, encoding);
		return resolver;
	}

	@Override
	public InputSource resolveEntity(String publicId, String systemId)
			throws SAXException, IOException {
		if (systemId == null)
			return null;
		String fileProtocol = "file://";
		InputSource inSource = null;
		if (systemId.startsWith(fileProtocol)) {
			String url = systemId.substring(fileProtocol.length());
			if (url.startsWith("/")) {
				url = url.substring(1);
			}
			String filePath = getRelativePath(url);
			if (web_base_path == null) {
				inSource = new InputSource(new InputStreamReader(
						new FileInputStream(filePath), encoding));
			} else {
				URL web_url = new URL(web_base_path + "/" + filePath);
				inSource = new InputSource(new InputStreamReader(
						web_url.openStream(), encoding));
			}
			inSource.setSystemId(UUID.randomUUID().toString());
		} else if (systemId.startsWith("http://")) {
			URL url = new URL(systemId);
			inSource = new InputSource(new InputStreamReader(url.openStream(),
					encoding));
			inSource.setSystemId(UUID.randomUUID().toString());
		} else {
			inSource = new InputSource(systemId);
		}
		return inSource;
	}

	/**
	 * @param url
	 * @return
	 */
	private String getRelativePath(String url) {
		String[] uArr = url.split("/|\\\\");
		String[] pArr = baseDir.split("/|\\\\");
		int i = 0;
		for (; i < uArr.length; i++) {
			String u = uArr[i];
			if (i == pArr.length)
				break;
			String p = pArr[i];
			if (!u.equals(p)) {
				break;
			}
		}
		if (i == pArr.length) {
			return this.mainXsdPath + '/' + url.substring(baseDir.length() + 1);
		}
		StringBuilder path = new StringBuilder(this.mainXsdPath + '/');
		for (int j = 0; j < pArr.length - (i + 1); j++) {
			path.append("../");
		}
		for (; i < uArr.length; i++) {
			path.append(uArr[i]);
			if (!uArr[i].toLowerCase().endsWith(".xsd")) {
				path.append('/');
			}
		}
		return path.toString();
	}

	private String web_base_path;

	public String getWebBasePath() {
		return web_base_path;
	}

	public void setWebBasePath(String web_base_path) {
		this.web_base_path = web_base_path;
	}

}